fix: normalize faster-whisper CUDA device args#18
Conversation
|
Thank you for raising your pull request and contributing to voscript. |
|
❤️ Great PR @MapleEve ❤️ The growth of project is inseparable from user feedback and contribution, thanks for your contribution! |
There was a problem hiding this comment.
Pull request overview
This PR improves model-loading compatibility and offline/cache behavior in the transcription pipeline by normalizing faster-whisper CUDA device arguments and making pyannote local snapshot loading use the file paths pyannote expects (including runtime-localized nested model references).
Changes:
- Normalize torch-style CUDA device strings (e.g.,
cuda:1) into faster-whisper-compatible kwargs (device="cuda",device_index=1). - Load pyannote diarization/embedding from cached local snapshots via expected local files, and generate a localized diarization config that points nested models at local weight files (with explicit failures for incomplete local artifacts).
- Add unit tests + changelog notes; update FOSSA workflow to run scan on
mainpushes and a diff test on PRs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
app/pipeline/orchestrator.py |
Implements faster-whisper device kwarg normalization and pyannote local-file resolution + config localization. |
tests/unit/test_pipeline_model_lifecycle.py |
Adds regression coverage for faster-whisper CUDA device kwarg normalization. |
tests/unit/test_huggingface_models.py |
Adds coverage for pyannote local snapshot file loading, nested-config localization, and safe failure modes. |
doc/changelog.en.md |
Documents the bug fixes in the English changelog for v0.7.5. |
doc/changelog.zh.md |
Documents the bug fixes in the Chinese changelog for v0.7.5. |
.github/workflows/fossa.yml |
Runs FOSSA scan on main pushes and adds a PR-only diff test step. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local_path = local_path / snapshot_filename | ||
|
|
||
| if not local_path.is_file(): | ||
| raise FileNotFoundError(f"Local pyannote model file not found: {local_path}") |
There was a problem hiding this comment.
Raising FileNotFoundError here includes the full local filesystem path in the exception message. That string is later surfaced to clients via the job status error field (see run_transcription storing str(e)), so this leaks host paths. Consider raising a public-safe error message (or a custom exception) that does not embed local_path, while still logging the full path server-side for debugging.
| raise FileNotFoundError(f"Local pyannote model file not found: {local_path}") | |
| component = next( | |
| ( | |
| component_name | |
| for expected_filename, component_name in _LOCAL_PYANNOTE_CONFIG_MODELS.values() | |
| if expected_filename == snapshot_filename | |
| ), | |
| "pyannote", | |
| ) | |
| logger.error("Local pyannote model file not found: %s", local_path) | |
| raise LocalPyannoteModelArtifactError( | |
| _public_safe_missing_pyannote_artifact(component) | |
| ) |
| ) | ||
| cache_dir.mkdir(parents=True, exist_ok=True) | ||
| localized_config = cache_dir / "config.yaml" | ||
| localized_config.write_text(localized_content, encoding="utf-8") |
There was a problem hiding this comment.
_localized_pyannote_config_path writes to a deterministic shared location under the system temp dir. In multi-worker deployments, two processes can race on write_text() and another reader may observe a partially written file. Consider writing to a temporary file and atomically renaming/replacing it (or using a file lock) to make this robust under concurrent startup/load.
| localized_config.write_text(localized_content, encoding="utf-8") | |
| temp_config_path: Path | None = None | |
| try: | |
| with tempfile.NamedTemporaryFile( | |
| mode="w", | |
| encoding="utf-8", | |
| dir=cache_dir, | |
| prefix="config.", | |
| suffix=".tmp", | |
| delete=False, | |
| ) as temp_config: | |
| temp_config.write(localized_content) | |
| temp_config.flush() | |
| temp_config_path = Path(temp_config.name) | |
| temp_config_path.replace(localized_config) | |
| except Exception: | |
| if temp_config_path is not None: | |
| temp_config_path.unlink(missing_ok=True) | |
| raise |
Summary
pytorch_model.binfilesValidation
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/unit/test_pipeline_model_lifecycle.py -v --tb=short --no-header-> 3 failed, 3 passed before faster-whisper implementationPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /tmp/voscript-faster-whisper-device-venv/bin/python -m pytest tests/unit/test_pipeline_model_lifecycle.py -v --tb=short --no-header-> 6 passedPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/unit/test_huggingface_models.py -v --tb=short --no-header-> 3 failed, 11 passed before nested pyannote config localizationPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/unit/test_huggingface_models.py -v --tb=short --no-header-> 14 passedPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/unit/test_huggingface_models.py tests/unit/test_pipeline_model_lifecycle.py tests/unit/test_pipeline_package.py -v --tb=short --no-header-> 22 passedPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /tmp/voscript-pr18-venv/bin/pytest tests/unit/ tests/test_security.py -v --tb=short --no-header-> 102 passedruff check app/ --ignore E501-> passedruff format --check app/-> passedpython public_release_scan.py --root <worktree>-> passedgit diff --check-> passedNote: the system Python security slice had only the local FastAPI stub available, so the full pytest slice was rerun in a clean
/tmpvenv with real FastAPI/httpx/numpy test dependencies.